/*******************************************************************************
*  file    : utils.hpp
*  created : 24.06.2014
*  author  : Slyshyk Oleksiy (alexSlyshyk@gmail.com)
*******************************************************************************/

#ifndef UTILS_HPP
#define UTILS_HPP

#include "find_patterns.hpp"
#include <QTextStream>
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include <QDebug>
#include <QVector>
#include <complex>
#include <vector>
#include <algorithm>
#include <cmath>
#include <type_traits>

namespace plugin_utils {

inline double sound_speed(double T) //T (температура газа, К)
{
    // http://www.fxyz.ru/%D1%84%D0%BE%D1%80%D0%BC%D1%83%D0%BB%D1%8B_%D0%BF%D0%BE_%D1%84%D0%B8%D0%B7%D0%B8%D0%BA%D0%B5/%D0%B0%D0%BA%D1%83%D1%81%D1%82%D0%B8%D0%BA%D0%B0/%D1%80%D0%B0%D1%81%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B2%D1%83%D0%BA%D0%B0/%D1%81%D0%BA%D0%BE%D1%80%D0%BE%D1%81%D1%82%D1%8C_%D0%B7%D0%B2%D1%83%D0%BA%D0%B0/%D1%81%D0%BA%D0%BE%D1%80%D0%BE%D1%81%D1%82%D1%8C_%D0%B7%D0%B2%D1%83%D0%BA%D0%B0_%D0%B2_%D0%B3%D0%B0%D0%B7%D0%B0%D1%85/
    static const double X = 1.4;   // χ (показатель адиабаты)
    static const double R = 287.0; //(газовая постоянная, Дж/кг·К)

    return std::sqrt(X*R*T);
}

template <typename T>
inline T linear_smooth_common(T x1, T y1, T x2, T y2, T x)
{
    T y,k; // result
    if( (x1-x2) != 0)
        k = ((y1 - y2) * (x-x2)) / (x1 - x2);
    else
        k = 0;
    y = k + y2;
    return y;
}

template <typename T>
inline T linear_smooth_integral(T x1, T y1, T x2, T y2, T x)
{
    T y; // result
    T k = ((y1 - y2) * (x-x2)) / (x1 - x2);
    y = k + y2;
    return y;
}

template <typename T>
inline T linear_smooth(T x1, T y1, T x2, T y2, T x)
{
    if(std::is_integral<T>::value)
        return linear_smooth_integral(x1,y1,x2,y2,x);
    else
        return linear_smooth_common  (x1,y1,x2,y2,x);

    return T();
}

template <typename T>
inline double get_func_value_linear(const T& data,
                                    const double &start_t,
                                    const double &dt     ,
                                    const double &t       )
{
    double res = 0.0;
    int idx_min = 0;
    int idx_max = 1;

    if(t < start_t)
        {
            idx_min = 0; idx_max = 1;
        }
    else if(t > (start_t + dt * data.size()))
        {
            idx_max = data.size() - 1; idx_min = idx_max - 1;
        }
    else
        {
            decltype(data.size()) i = static_cast<int>((t - start_t) / dt) ;
            for(; i < data.size(); ++i)
                {
                    if( (i*dt + start_t) > t)
                        {
                            idx_max = i; idx_min = i - 1;
                            break;
                        }
                }
        }

    res = linear_smooth(idx_min*dt+ start_t, std::real(data[idx_min]), idx_max*dt+ start_t, std::real(data[idx_max]), t);

    return res;
}

template <typename T>
inline T get_func_value_linear(const std::vector<T,std::allocator<T>>& data,
                                    const T& start_t                       ,
                                    const T& dt                            ,
                                    const T& t                              )
{
    T res = 0.0;
    typename std::vector<T,std::allocator<T>>::size_type idx_min = 0;
    typename std::vector<T,std::allocator<T>>::size_type idx_max = 1;

    if(t < start_t)
        {
            idx_min = 0; idx_max = 1;
        }
    else if(t > (start_t + dt * static_cast<T>(data.size())))
        {
            idx_max = data.size() - 1; idx_min = idx_max - 1;
        }
    else
        {
            decltype(data.size()) i =
                    static_cast<typename std::vector<T,std::allocator<T>>::size_type>((t - start_t) / dt) ;
            for(; i < data.size(); ++i)
                {
                    if( static_cast<T>(i*dt + start_t) > t)
                        {
                            idx_max = i; idx_min = i - 1;
                            break;
                        }
                }
        }

    res = linear_smooth(static_cast<T>(idx_min)*dt+ start_t,
                        data[idx_min],
                        static_cast<T>(idx_max)*dt+ start_t,
                        data[idx_max],
                        t);

    return res;
}

inline double square_smooth(double x1, double y1, double x2, double y2, double x3, double y3, double x)
{
    double y,a,b,c; // result

    a = (y3 - ( (x3*(y2-y1) + x2*y1 - x1*y2) / (x2 - x1) )) / ( x3*(x3-x1-x2) +x1*x2 ) ;
    b = ( (y2 - y1) / (x2 - x1) ) - (a*(x1+x2));
    c = ( (x2*y1 - x1*y2) / (x2 - x1) ) + a*x1*x2 ;

    y = a*x*x + b*x + c;
    return y;
}

template <typename T>
inline double get_func_value_square(const T&data, const double &start_t,
                                    const double &dt, const double &t)
{
    double res = 0.0;
    int idx_0 = 0;
    int idx_1 = 0;
    int idx_2 = 0;

    if(t < start_t)
        {
            idx_0 = 0;idx_1 = 1;idx_2 = 2;
        }
    else if(t > (start_t + dt * ( data.size() )))
        {
            idx_2 = data.size() - 1; idx_1 = idx_2 - 1; idx_0 = idx_1 - 1;
        }
    else
        {
            decltype(data.size()) i = 0;
            i = (t - start_t) /dt ;
            for(; i < (data.size()-1); ++i)
                {
                    if( (i*dt + start_t) > t)
                        {
                            idx_1 = i; idx_0 = i - 1; idx_2 = i + 1;
                            break;
                        }
                }
        }

    if(idx_2 == 0)
        return 0.0;

    res = square_smooth(idx_0*dt+ start_t,std::real(data[idx_0]),
                        idx_1*dt+ start_t,std::real(data[idx_1]),
                        idx_2*dt+ start_t,std::real(data[idx_2]),t);

    return res;
}

void normalize(std::vector<std::complex<double>>& data, double n);

template <typename T>
inline void normalize(std::vector<T,std::allocator<T>>& data, T n)
{
    if(data.size() == 0)
        {
            qDebug() << Q_FUNC_INFO << "  Retreive Zero array.";
            return;
        }
    if( n > 0)
        {
            auto max = std::max_element(data.cbegin(),data.cend(),std::less<T>());
            T norm_koeff;
            norm_koeff = (*max) / n;
            auto begin = data.begin();
            auto end   = data.end();
            if(norm_koeff != 0)
                std::transform(begin, end, begin, [norm_koeff](const T& d){return d / norm_koeff;} );
        }
}

template <typename T>
std::vector<T,std::allocator<T>> read_simple_from_file(const QString &file_name)
{
    std::vector<T,std::allocator<T>> sample;
    double  dataTime;
    QFile file(file_name);
    if(!file.open(QIODevice::Text | QIODevice::ReadOnly))
        {
            qWarning() << "Can't open "<< file_name;
            return sample;
        }

    int cnt = -1;
    QTextStream out(&file);

    out >> cnt >> dataTime;
    //qDebug() <<file_name << "header:" << cnt << " " << dataTime ;

    sample.reserve(cnt);
    try
    {
        double d;
        while( !out.atEnd())
            {
                out >> d;
                sample.push_back(d);
            }
        if(static_cast<decltype(cnt)>(sample.size()) > cnt)
            sample.resize(cnt);
    }
    catch(std::exception & e)
    {
        file.close();
        qDebug() << e.what();
        sample.resize(0);
        return sample;
    }

    if(cnt != static_cast<decltype(cnt)>(sample.size()))
        qDebug() << QString("Read CNT = %1, but real samples cnt = %2 .").arg(cnt).arg(sample.size());

    file.close();

    return sample;
}

bool save_simple_to_file(const QString& file_name, const std::vector<std::complex<double> > &data, double time = 0.);

QStringList fileList(const QString& str);

} //namespace plugin_utils

#endif // UTILS_HPP
